# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
NotifyIconMessageLoop.py
By: Denver Coneybeare <denver@sleepydragon.org>
Nov 14, 2012
"""

from __future__ import print_function
from __future__ import unicode_literals

from winnotify import Error

import contextlib
import threading
import uuid

class NotifyIconMessageLoop(threading.Thread):
    """
    A thread whose run() method loops over the Windows message queue,
    allowing an icon in the Windows Notification Area to be fully functional.
    """

    def __init__(self, winapi=None):
        """
        Initializes a new instance of this class.
        *winapi* must be the winapi module or a compatible object and will be
        used to perform the low-level operations required by this class; it is
        recommended to simply specify None (the default) in order to use the
        winapi module directly; this argument is primarily intended for use
        during unit testing of this class, and should not normally be explicitly
        specified.
        """
        threading.Thread.__init__(self)

        if winapi is None:
            import winapi as winapi_module
            winapi = winapi_module

        self.winapi = winapi
        self.window_class_name = None


    def run(self):
        """
        Initializes then runs the message loop.
        This method is not normally invoked directly, but rather in a new
        thread started by invoking self.start().
        """
        with self.register_window_class() as window_class_atom:
            pass


    @contextlib.contextmanager
    def register_window_class(self, name=None):
        """
        Invokes RegisterClassEx() to register the window class.
        This method returns an object that supports the context management
        protocol, and will register the window class when entered, return the
        class atom, then unregister the window class when exited.
        *name* must be a Unicode string whose value is the name of the window
        class to register; may be None (the default) in which case a globally-
        unique window class name will be generated by
        self.generate_window_class_name() and used.
        Raises winnotify.Error on error.
        """
        winapi = self.winapi

        if name is None:
            name = self.generate_window_class_name()

        # populate the WNDCLASSEX struct with properites of the window class
        wndClass = winapi.WNDCLASSEX()
        wndClass.cbSize = winapi.WNDCLASSEX_SIZE
        wndClass.lpfnWndProc = winapi.WNDPROC(self.wnd_proc)
        wndClass.lpszClassName = name
        hInstance = winapi.GetModuleHandle()
        wndClass.hInstance = hInstance

        # register the window class with RegisterClassEx
        class_atom = winapi.RegisterClassEx(wndClass)
        if class_atom == 0:
            (errno, message) = winapi.get_last_error()
            raise Error("unable to register window class \"{}\" via "
                "RegisterClassEx(): {} ({})".format(name, message, errno))

        # return the class atom
        yield class_atom

        # unregister the window class with UnregisterClass
        unregister_success = winapi.UnregisterClass(name, hInstance)
        if not unregister_success:
            (errno, message) = winapi.get_last_error()
            raise Error("unable to unregister window class \"{}\" via "
                "UnregisterClass(): {} ({})".format(name, message, errno))


    def generate_window_class_name(self):
        """
        Generates and returns a string that can be used as a window class
        name.  This method returns a unique string each time that it is invoked.
        Returns a Unicode string whose value is the window class name.
        """
        u = uuid.uuid4()
        key_bytes = u.hex
        key = key_bytes.decode("US-ASCII")
        name = "NotifyIconWindowClass-{}".format(key)
        return name


    def wnd_proc(self, hwnd, message, wparam, lparam):
        """
        The "window procedure" used by the Window Class registered by
        self.register_window_class().
        """
        winapi = self.winapi
        result = winapi.DefWindowProc(hwnd, message, wparam, lparam)
        return result


if __name__ == "__main__":
    x = NotifyIconMessageLoop()
    x.run()